package com.weic.easycache.framework;

import java.io.File;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import com.j256.ormlite.android.apptools.OpenHelperManager;
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;
import com.j256.ormlite.dao.CloseableIterator;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.stmt.DeleteBuilder;
import com.j256.ormlite.stmt.UpdateBuilder;
import com.j256.ormlite.stmt.Where;
import com.weic.easycache.Debug;
import com.weic.easycache.exception.FileCreateFailureException;
import com.weic.easycache.exception.SdCardNotMountedException;
import com.weic.easycache.exception.SdCardNotValidException;
import com.weic.easycache.file.FileUtils;
import com.weic.easycache.file.FStoreLoc;
import com.weic.easycache.framework.db.ApiCacheTable;
import com.weic.easycache.framework.db.ApiCacheOrmLiteHelper;
import com.weic.easycache.framework.db.Table;
import com.weic.easycache.framework.user.IUser;
import com.weic.easycache.framework.user.IUserConfig;
import com.weic.easycache.framework.web.Api;
import com.weic.easycache.helper.MsgHelper;
import com.weic.easycache.helper.SharedPrefHelper;
import com.weic.easycache.utils.BitmapUtils;

public class DataManager {
	public static final String TAG = DataManager.class.getSimpleName();

	//需要值被弱引用，而不是key。因此弃用 WeakHashMap
	private Map<String, WeakReference<?>> dataCache = new ConcurrentHashMap<String, WeakReference<?>>();
	private Map<String, WeakReference<?>> proxyCache = new HashMap<String, WeakReference<?>>();

	private static int instanceCount = 0;
	private volatile boolean mStopped = true, mDestroyed = false;
	private volatile Handler mHandler;
	private volatile Context mContext;

	/*不要用单例模式，否则不好响应各个Activity的生命周期*/
	public DataManager(Context context) {
		instanceCount++;
		mHandler = new UiHandler(context.getMainLooper());

		mContext = context;
	}

	public <U extends IUser> U getUser() {
		return UserHelper.getUser();
	}

	public <U extends IUserConfig> U getUserConfig() {
		return UserHelper.getUserConfig();
	}

	/**从网络或数据库加载数据**/
	@SuppressWarnings("unchecked")
	public <P extends Param, D> void loadData(Request request, P param, Callback<D> callback) {
		if(param == null) param = (P)new Param();
		String key = KeyUtils.makeMemoryCacheKey(request, param);
		D data = null;
		if(!param.refresh && !param.ignoreCache) {
			data = getValue(key, dataCache);
		}else {
			dataCache.remove(key);
		}
		callback.start(this);
		if(data != null) {
			callback.deliverAndPost(callback.obtain(Result.STATUS_COMPLETE, data));
		}else {
			Proxy<P, D> proxy = getProxy(key, request, param, callback);
			proxy.loadData();
		}
	}

	/**向服务端上传数据**/
	public <P extends Param, D> void upLoadData(Request request, P param, Callback<D> callback) {
		loadData(request, param, callback);
	}

	public boolean clearApiCache(Api<?>... apis) {
		boolean result = false;
		List<String> list = new ArrayList<String>(apis.length);
		for(Api<?> api : apis) {
			list.add(KeyUtils.getCategory(api));
		}
		OrmLiteSqliteOpenHelper helper = getDBHelper();
		try {
			Dao<ApiCacheTable, String> dao = helper.getDao(ApiCacheTable.class);
			DeleteBuilder<ApiCacheTable, String> deleteBuilder = dao.deleteBuilder();
			deleteBuilder.where().in(ApiCacheTable.FIELD_CATEGORY, list);
			deleteBuilder.delete();
			result = true;
		} catch (SQLException e) {
			result = false;
			e.printStackTrace();
		}
		releaseDBHelper();
		return result;
	}

	public <T, ID> T loadDBEntity(Table<T, ID> table, ID id) {
		T result = null;
		OrmLiteSqliteOpenHelper helper = getDBHelper();
		Dao<T, ID> dao = null;
		try {
			dao = table.getDao(helper);
			result = dao.queryForId(id);
		} catch (SQLException e) {
			if(dao != null) try { dao.closeLastIterator(); } catch (SQLException e1) {}
			e.printStackTrace();
		}
		releaseDBHelper();
		return result;
	}

	/**本方法要慎用，防止数据量过大**/
	public <T, ID> List<T> loadDBEntitys(Table<T, ID> table) {
		List<T> result = null;
		OrmLiteSqliteOpenHelper helper = getDBHelper();
		Dao<T, ID> dao = null;
		try {
			dao = table.getDao(helper);
			result = dao.queryForAll();
		} catch (SQLException e) {
			if(dao != null) try { dao.closeLastIterator(); } catch (SQLException e1) {}
			e.printStackTrace();
		}
		releaseDBHelper();
		return result;
	}

	/**取完数据之后应该关闭iterator.close()**/
	public <T, ID> CloseableIterator<T> loadDBIterator(Table<T, ID> table) {
		CloseableIterator<T> result = null;
		OrmLiteSqliteOpenHelper helper = getDBHelper();
		Dao<T, ID> dao = null;
		try {
			dao = table.getDao(helper);
			result = dao.iterator();
		} catch (SQLException e) {	//由于dao.iterator()不会抛异常，而收到异常的时候dao必然为null，所以为下面的语句永远不会执行
			//if(dao != null) try { dao.closeLastIterator(); } catch (SQLException e1) {}
			e.printStackTrace();
		}
		releaseDBHelper();
		return result;
	}

	/**@param fieldValueMap map的key-value表示查询条件，注意key表示字段名，不一定跟实体类的变量名相同，
	 * 如果有此需求，应该给实体类的变量指定常量表名，同时注意升级数据库**/
	public <T, ID> List<T> loadDBEntitys(Table<T, ID> table, Map<String, Object> fieldValueMap) {
		List<T> result = null;
		OrmLiteSqliteOpenHelper helper = getDBHelper();
		Dao<T, ID> dao = null;
		try {
			dao = table.getDao(helper);
			result = dao.queryForFieldValuesArgs(fieldValueMap);
		} catch (SQLException e) {
			if(dao != null) try { dao.closeLastIterator(); } catch (SQLException e1) {}
			e.printStackTrace();
		}
		releaseDBHelper();
		return result;
	}

	public <T, ID> boolean saveDBEntity(Table<T, ID> table, T entity, boolean overwrite) {
		boolean result = false;
		OrmLiteSqliteOpenHelper helper = getDBHelper();
		try {
			Dao<T, ID> dao = table.getDao(helper);
			if(overwrite) {
				dao.createOrUpdate(entity);
			}else {
				dao.createIfNotExists(entity);
			}
			result = true;
		} catch (SQLException e) {
			result = false;
			e.printStackTrace();
		}
		releaseDBHelper();
		return result;
	}

	/**只能用于少量数据保存，而大数据量的数据库操作通常不会发生在前台。若有需求，请使用{@link #saveDBEntitys(Request, P, Callback)}**/
	public <T, ID> boolean saveDBEntitys(Table<T, ID> table, List<T> entities, boolean overwrite) {
		boolean result = false;
		OrmLiteSqliteOpenHelper helper = getDBHelper();
		try {
			Dao<T, ID> dao = table.getDao(helper);
			for(T entity : entities) {
				if(overwrite) {
					dao.createOrUpdate(entity);
				}else {
					dao.createIfNotExists(entity);
				}
			}
			result = true;
		} catch (SQLException e) {
			result = false;
			e.printStackTrace();
		}
		releaseDBHelper();
		return result;
	}

	/**大数据量的数据库操作**/
	public <P extends Param, D> void saveDBEntitys(Request request, P param, Callback<D> callback) {
		loadData(request, param, callback);
	}

	/**根据ID更新表中的所有字段**/
	public <T, ID> boolean updateDBEntity(Table<T, ID> table, T entity) {
		boolean result = false;
		OrmLiteSqliteOpenHelper helper = getDBHelper();
		try {
			Dao<T, ID> dao = table.getDao(helper);
			dao.update(entity);
			result = true;
		} catch (SQLException e) {
			result = false;
			e.printStackTrace();
		}
		releaseDBHelper();
		return result;
	}

	/**仅更新ID字段，根据entity中旧的ID**/
	public <T, ID> boolean updateDBEntity(Table<T, ID> table, T entity, ID id) {
		boolean result = false;
		OrmLiteSqliteOpenHelper helper = getDBHelper();
		try {
			Dao<T, ID> dao = table.getDao(helper);
			dao.updateId(entity, id);
			result = true;
		} catch (SQLException e) {
			result = false;
			e.printStackTrace();
		}
		releaseDBHelper();
		return result;
	}

	public <T, ID> boolean updateDBEntity(Table<T, ID> table, String[] columns, Object[] values) {
		boolean result = false;
		OrmLiteSqliteOpenHelper helper = getDBHelper();
		try {
			Dao<T, ID> dao = table.getDao(helper);
			UpdateBuilder<T, ID> updateBuilder = dao.updateBuilder();
			for(int i=0; i<columns.length; i++) {
				updateBuilder.updateColumnValue(columns[i], values[i]);
			}
			updateBuilder.update();
			result = true;
		} catch (SQLException e) {
			result = false;
			e.printStackTrace();
		}
		releaseDBHelper();
		return result;
	}

	public <T, ID> boolean deleteDBEntity(Table<T, ID> table, ID id) {
		boolean result = false;
		OrmLiteSqliteOpenHelper helper = getDBHelper();
		try {
			Dao<T, ID> dao = table.getDao(helper);
			dao.deleteById(id);
			result = true;
		} catch (SQLException e) {
			result = false;
			e.printStackTrace();
		}
		releaseDBHelper();
		return result;
	}

	public <T, ID> boolean deleteDBEntity(Table<T, ID> table, List<ID> ids) {
		boolean result = false;
		OrmLiteSqliteOpenHelper helper = getDBHelper();
		try {
			Dao<T, ID> dao = table.getDao(helper);
			dao.deleteIds(ids);
			result = true;
		} catch (SQLException e) {
			result = false;
			e.printStackTrace();
		}
		releaseDBHelper();
		return result;
	}

	public <T, ID> boolean deleteDBEntity(Table<T, ID> table, String[] columns, Object[] values) {
		boolean result = false;
		OrmLiteSqliteOpenHelper helper = getDBHelper();
		try {
			Dao<T, ID> dao = table.getDao(helper);
			DeleteBuilder<T, ID> deleteBuilder = dao.deleteBuilder();
			Where<T, ID> where = deleteBuilder.where();
			boolean first = true;
			for(int i=0; i<columns.length; i++) {
				if(first) {
					first = false;
				}else {
					where.and();
				}
				where.eq(columns[i], values[i]);
			}
			deleteBuilder.delete();
			result = true;
		} catch (SQLException e) {
			result = false;
			e.printStackTrace();
		}
		releaseDBHelper();
		return result;
	}

	public void saveString(String key, String value) {
		edit().putString(key, value).commit();
	}

	public String getString(String key, String defaultValue) {
		return getSharedPref().getString(key, defaultValue);
	}

	public void saveBoolean(String key, boolean value) {
		edit().putBoolean(key, value).commit();
	}

	public boolean getBoolean(String key, boolean defaultValue) {
		return getSharedPref().getBoolean(key, defaultValue);
	}

	public void saveInt(String key, int value) {
		edit().putInt(key, value).commit();
	}

	public int getInt(String key, int defaultValue) {
		return getSharedPref().getInt(key, defaultValue);
	}

	public void saveFloat(String key, float value) {
		edit().putFloat(key, value).commit();
	}

	public float getFloat(String key, float defaultValue) {
		return getSharedPref().getFloat(key, defaultValue);
	}

	public void saveLong(String key, long value) {
		edit().putLong(key, value).commit();
	}

	public long getLong(String key, long defaultValue) {
		return getSharedPref().getLong(key, defaultValue);
	}

	public boolean saveImage(String name, Bitmap bitmap) {
		String path = getImagePath(name);
		if(path == null) return false;
		BitmapUtils.saveImage(path, bitmap);
		return true;
	}

	public Bitmap readImage(String name) {
		return BitmapUtils.readImage(FStoreLoc.DEFAULT.getExistsFileOrDir(mContext, FStoreLoc.IMAGES_CACHE + File.separator + name), null);
	}

	public Bitmap readImage(String name, int width, int height) {
		return BitmapUtils.readImage(FStoreLoc.DEFAULT.getExistsFileOrDir(mContext, FStoreLoc.IMAGES_CACHE + File.separator + name), width, height);
	}

	public Bitmap readImage(int drawableId, int width, int height) {
		return BitmapUtils.readImage(mContext.getResources(), drawableId, width, height);
	}

	public boolean isImageExists(String name) {
		String path = getImagePath(name);
		if(path == null) return false;
		return FileUtils.isExistsFile(path);
	}

	public String getImagePath(String name) {
		try {
			return FStoreLoc.DEFAULT.getImagesCacheDir(mContext).getPath() + File.separator + name;
		} catch (SdCardNotMountedException e) {	//由于是在非外置卡上存储，所以不存在卡未挂载和无效的情况
			e.printStackTrace();
		} catch (SdCardNotValidException e) {
			e.printStackTrace();
		} catch (FileCreateFailureException e) {
			e.printStackTrace();
		}
		return null;
	}

	public Editor edit() {
		return getSharedPref().edit();
	}

	private SharedPreferences getSharedPref() {
		return SharedPrefHelper.DataManagerS.getSharedPref(mContext);
	}

	private <P extends Param, D> Proxy<P, D> getProxy(String key, Request request, P param, Callback<D> callback) {
		Proxy<P, D> proxy = getValue(key, proxyCache);
		if(proxy == null) {
			proxy = new Proxy<P, D>(mContext, this, key, request, param, callback);
			proxyCache.put(key, new WeakReference<Proxy<P, D>>(proxy));
		}else {
			proxy.update(callback);
		}
		return proxy;
	}

	@SuppressWarnings("unchecked")
	private static <T, R extends Reference<?>> T getValue(String key, Map<String, R> cache) {
		T value = null;
		R ref = cache.get(key);
		if(ref != null) {
			value = (T)ref.get();
		}
		return value;
	}

	void onCreate() {
		mStopped = false;
	}

	void onRestart() {
		mStopped = false;
	}

	void onStop() {
		mStopped = true;
		mHandler.removeMessages(MSG_UPDATE);
		//不缓存太多数据，减少内存占用
		dataCache.clear();
		proxyCache.clear();
	}

	void onDestroy() {
		mDestroyed = true;
		mContext = null;
		//mHandler = null;
		dataCache = null;
		proxyCache = null;

		if(--instanceCount <= 0) {
			//不用删除用户信息，下次进来可能还是登录状态
			//UserHelper.mUser = null;
			//UserHelper.mUserConfig = null;

			/**不在这里关闭数据库，以免导致异常（即使程序退出，可能后台任务还没完成），而是通过引用计数的方式自动关闭。**/
			/* 关闭数据库。不要在每次查询完毕的时候关闭，否则导致异常。查询完毕只需要关闭Cursor或者Connection
			 * 或者Itrator的最后一个连接就可以了。详见OrmLite延迟加载遍历集合对象。*/
			/*if(mDBHelper != null) {
				OrmLiteSqliteOpenHelper helper = mDBHelper;
				mDBHelper = null;	//先置为null，再关闭。以防止获取到已关闭的对象。
				helper.close();
			}*/
		}
	}

	/**即使不同App共享同一进程，也不会共享静态变量。由于ClassLoader不同**/
	OrmLiteSqliteOpenHelper getDBHelper() {
		//直接调用而不用单例，因为直接调用会有引用计数，会自动释放。（这两个方法有同步操作）
		return OpenHelperManager.getHelper(mContext.getApplicationContext(), ApiCacheOrmLiteHelper.class);
	}

	void releaseDBHelper() {
		//与getHelper调用次数相对，会自动释放Helper。（这两个方法有同步操作）
		OpenHelperManager.releaseHelper();
	}

	public Context getContext() {
		return mContext;
	}

	void postWaitingLoading(Callback<?> callback, boolean loading, String message) {
		if(mDestroyed) return;
		Result<?> r = callback.obtain();
		r.taskid = 0;
		r.progress = 0;
		r.data = null;
		r.status = loading ? Result.STATUS_LOADING : Result.STATUS_WAITING;
		r.message = message;
		callback.deliverAndPost(r);
	}

	<D> void postProgress(Callback<D> callback, D data, int taskId, float progress, String message, int errorType, int errorCode) {
		Context context = mContext;
		if(mDestroyed) return;

		Result<D> r = callback.obtain();
		r.taskid = taskId;
		r.progress = progress;
		r.data = data;
		r.status = Result.STATUS_PROGRESS;
		r.message = MsgHelper.resolveMessage(context, r.status, message, errorType, errorCode);
		callback.deliverAndPost(r);
	}

	void postFailure(Callback<?> callback, int errorType, int errorCode, String message) {
		Context context = mContext;
		if(mDestroyed) return;

		Result<?> r = callback.obtain();
		r.taskid = 0;
		r.data = null;
		r.status = Result.STATUS_FAILURE;
		r.message = MsgHelper.resolveMessage(context, r.status, message, errorType, errorCode);
		if(errorType == Errors.NEED_LOGIN) {
			UserHelper.invalidate();	//注销登录状态
		}
		callback.deliverAndPost(r, true);
	}

	<D> void postComplete(Callback<D> callback, D data, String key, boolean cache, boolean end) {
		if(mDestroyed) return;
		Result<D> r = callback.obtain();
		r.taskid = 0;
		r.progress = 100;
		r.data = data;
		r.status = Result.STATUS_COMPLETE;
		r.message = null;
		callback.deliverAndPost(r, end);
		Map<String, WeakReference<?>> dcache = dataCache;
		if(!mStopped && cache && dcache != null) {	//不用过问用户信息，以简化复杂性。所有关于用户的处理由UserHelper处理
			dcache.put(key, new WeakReference<D>(data));
		}
	}

	<D> void postUpdate(Callback<D> callback, D data, String key, boolean cache) {
		if(mDestroyed) return;
		Result<D> r = callback.obtain();
		r.taskid = 0;
		r.progress = 100;
		r.data = data;
		r.status = Result.STATUS_UPDATE;
		r.message = null;
		callback.deliverAndPost(r, true);
		Map<String, WeakReference<?>> dcache = dataCache;
		if(!mStopped && cache && dcache != null) {	//不用过问用户信息，以简化复杂性。所有关于用户的处理由UserHelper处理
			dcache.put(key, new WeakReference<D>(data));
		}
	}

	private static final int MSG_UPDATE		= 0;
	private static class UiHandler extends Handler {
		UiHandler(Looper looper) {
			super(looper);
		}

		@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case MSG_UPDATE:
				((DataManager.Callback<?>)msg.obj).doUpdate();
				break;
			}
		}
	}

	public static abstract class Callback<D> {
		private final Queue<Result<D>> ready = new ConcurrentLinkedQueue<Result<D>>();
		private volatile boolean started = false;
		private volatile DataManager dm;
		private volatile Result<D> endr;

		/*public*/ void start(DataManager dm) {
			this.dm = dm;
			started = true;
		}

		/*public*/ Result<D> obtain() {
			return obtain(0, null);
		}

		/*public*/ Result<D> obtain(int status, D data) {
			Result<D> r = Result.obtain();
			r.status = status;
			r.data = data;
			return r;
		}

		/*public*/ void deliverAndPost(Result<?> rs) {
			deliverAndPost(rs, false);
		}

		@SuppressWarnings("unchecked")
		/*public*/ void deliverAndPost(Result<?> rs, boolean end) {
			if(Debug.LOG) Log.i(TAG, "Callback.deliverAndPost~~~~~0");
			if(!started) return;
			if(rs == null) return;
			if(endr != null) return;
			Result<D> r = (Result<D>)rs;
			if(end) endr = r;
			ready.offer(r);
			if(Debug.LOG) Log.i(TAG, "Callback.deliverAndPost~~~~~ready.size: "+ready.size());
			dm.mHandler.obtainMessage(MSG_UPDATE, this).sendToTarget();
		}

		private void reset() {
			started = false;
			dm.mHandler.removeMessages(MSG_UPDATE, this);
			while(ready.size() > 0) {
				free(ready.poll());
			}
			ready.clear();
			endr = null;
			onFinish();
		}

		private void free(Result<D> r) {
			r.recycle();
		}

		/**UI线程调用**/
		/*public*/ void doUpdate() {
			while(ready.size() > 3) {
				free(ready.poll());
				if(Debug.LOG) Log.i(TAG, "Callback.doUpdate.削减过长消息~~~~~ready.size: "+ready.size());
			}
			Result<D> r = ready.poll();
			if(r == null) return;
			if(dm.mStopped) {
				if(r == endr) onUpdate(r);
			}else {
				onUpdate(r);
			}
			free(r);
			if(r == endr) reset();
		}

		protected abstract void onUpdate(Result<D> result);

		protected void onFinish() {}
	}
}
